/*
 * Copyright (c) 2021 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include "board.h"
#include "hpm_debug_console.h"
#include "hpm_spi_drv.h"
#ifdef HPMSOC_HAS_HPMSDK_DMAV2
#include "hpm_dmav2_drv.h"
#else
#include "hpm_dma_drv.h"
#endif
#include "hpm_dmamux_drv.h"
#include "hpm_gpio_drv.h"
#include "hpm_l1c_drv.h"
#include "lvgl.h"

#define SPI_ENABLE_DMA

#define HORIZON
#define LCD_WIDTH   172
#define LCD_HEIGHT  320
#define LCD_OFFSET  ((240-LCD_WIDTH)/2)

#ifdef HORIZON
#define LV_LCD_WIDTH  LCD_HEIGHT
#define LV_LCD_HEIGHT LCD_WIDTH
#else
#define LV_LCD_WIDTH  LCD_WIDTH
#define LV_LCD_HEIGHT LCD_HEIGHT
#endif

/* data width definition */
#define LCD_SPI_DATA_LEN_IN_BIT          (8U)
#define LCD_SPI_DATA_LEN_IN_BYTE         (1U)
#define LCD_SPI_BYTES_PER_TRANS_MAX      (512)

#define LCD_SPI               HPM_SPI3
#define LCD_SPI_CLOCK         clock_spi3
#define LCD_SPI_SCLK_FREQ     (40000000UL)

static spi_control_config_t lcd_control_config = {0};
static uint32_t one_byte_ctrl = 0;
hpm_stat_t lcd_spi_send_cmd(uint8_t reg);
struct lv_adapter {
    lv_disp_draw_buf_t draw_buf;
    lv_disp_drv_t disp_drv;
};

static struct lv_adapter lv_adapter_ctx;

#ifdef SPI_ENABLE_DMA

#define LCD_SPI_DMA           HPM_HDMA
#define LCD_SPI_DMAMUX        HPM_DMAMUX
#define LCD_SPI_TX_DMA_REQ    HPM_DMA_SRC_SPI3_TX
#define LCD_SPI_TX_DMA_CH     1
#define LCD_SPI_TX_DMAMUX_CH  DMA_SOC_CHN_TO_DMAMUX_CHN(LCD_SPI_DMA, LCD_SPI_TX_DMA_CH)
#define LCD_SPI_DMA_IRQ       IRQn_HDMA

#define LCD_SPI_DMA_TRANS_DATA_WIDTH     DMA_TRANSFER_WIDTH_BYTE

#ifndef PLACE_BUFF_AT_CACHEABLE
#define PLACE_BUFF_AT_CACHEABLE 1
#endif

volatile bool spi_tx_dma_trans_done = false;

typedef struct {
    uint8_t *trans_buf;
    uint32_t trans_size;
    uint32_t trans_count;
} SPI_DMA_STATE;

volatile SPI_DMA_STATE lcd_spi_dma_tx={NULL,0,0};


hpm_stat_t spi_tx_trigger_dma(DMA_Type *dma_ptr, uint8_t ch_num, SPI_Type *spi_ptr, uint32_t src, uint8_t data_width, uint32_t size)
{
    dma_handshake_config_t config;

    dma_default_handshake_config(dma_ptr, &config);
    config.ch_index = ch_num;
    config.dst = (uint32_t)&spi_ptr->DATA;
    config.dst_fixed = true;
    config.src = src;
    config.src_fixed = false;
    config.data_width = data_width;
    config.size_in_byte = size;

    return dma_setup_handshake(dma_ptr, &config, true);
}

void isr_spi_dma(void)
{
    volatile hpm_stat_t tx_stat;
    uint32_t trans_once;
    tx_stat = dma_check_transfer_status(LCD_SPI_DMA, LCD_SPI_TX_DMA_CH);
    if (tx_stat & (DMA_CHANNEL_STATUS_TC | DMA_CHANNEL_STATUS_ERROR | DMA_CHANNEL_STATUS_ABORT)) {
        if(lcd_spi_dma_tx.trans_count >= lcd_spi_dma_tx.trans_size)
        {
            lcd_spi_send_cmd(0x29);
            spi_tx_dma_trans_done = true;
            lv_disp_flush_ready(&lv_adapter_ctx.disp_drv);
        }
        else
        {
            trans_once = lcd_spi_dma_tx.trans_size - lcd_spi_dma_tx.trans_count;
            if(trans_once > LCD_SPI_BYTES_PER_TRANS_MAX)
            {
                trans_once = LCD_SPI_BYTES_PER_TRANS_MAX;
            }
            if(status_success == spi_setup_dma_transfer(LCD_SPI, &lcd_control_config, NULL, NULL, trans_once, 0))
            {
                dmamux_config(LCD_SPI_DMAMUX, LCD_SPI_TX_DMAMUX_CH, LCD_SPI_TX_DMA_REQ, true);
                if(status_success == spi_tx_trigger_dma(LCD_SPI_DMA, LCD_SPI_TX_DMA_CH,LCD_SPI, core_local_mem_to_sys_address(BOARD_RUNNING_CORE, (uint32_t)(lcd_spi_dma_tx.trans_buf+lcd_spi_dma_tx.trans_count)), LCD_SPI_DMA_TRANS_DATA_WIDTH, trans_once))
                {
                    lcd_spi_dma_tx.trans_count += trans_once;
                }
            }
        }
    }
}
SDK_DECLARE_EXT_ISR_M(LCD_SPI_DMA_IRQ, isr_spi_dma)
#endif

#define LV_FB_SIZE_ALIGNED HPM_L1C_CACHELINE_ALIGN_UP(LV_LCD_WIDTH * LV_LCD_HEIGHT)

#ifndef HPM_LVGL_FRAMEBUFFER_NONCACHEABLE
static lv_color_t __attribute__((section(".framebuffer"), aligned(HPM_L1C_CACHELINE_SIZE))) lv_framebuffer0[LV_FB_SIZE_ALIGNED];
static lv_color_t __attribute__((section(".framebuffer"), aligned(HPM_L1C_CACHELINE_SIZE))) lv_framebuffer1[LV_FB_SIZE_ALIGNED];
#else
static lv_color_t __attribute__((section(".noncacheable"), aligned(HPM_L1C_CACHELINE_SIZE))) lv_framebuffer0[LV_FB_SIZE_ALIGNED];
static lv_color_t __attribute__((section(".noncacheable"), aligned(HPM_L1C_CACHELINE_SIZE))) lv_framebuffer1[LV_FB_SIZE_ALIGNED];
#endif
static lv_color_t *lcdc_framebuffer = lv_framebuffer1;

void LCD_SPI_Init(void)
{
    spi_timing_config_t timing_config = {0};
    spi_format_config_t format_config = {0};
    uint32_t spi_clcok;

    spi_clcok = board_init_spi_clock(LCD_SPI);
    board_init_spi_pins(LCD_SPI);

    /* set SPI sclk frequency for master */
    spi_master_get_default_timing_config(&timing_config);
    timing_config.master_config.clk_src_freq_in_hz = spi_clcok;
    timing_config.master_config.sclk_freq_in_hz = LCD_SPI_SCLK_FREQ;
    if (status_success != spi_master_timing_init(LCD_SPI, &timing_config)) {
        printf("SPI master timing init failed\n");
    }

    /* set SPI format config for master */
    spi_master_get_default_format_config(&format_config);
    format_config.common_config.data_len_in_bits = LCD_SPI_DATA_LEN_IN_BIT;
    format_config.common_config.mode = spi_master_mode;
    format_config.common_config.cpol = spi_sclk_high_idle;
    format_config.common_config.cpha = spi_sclk_sampling_even_clk_edges;
    spi_format_init(LCD_SPI, &format_config);

    /* set SPI control config for master */
    spi_master_get_default_control_config(&lcd_control_config);
    lcd_control_config.master_config.cmd_enable = false;  /* cmd phase control for master */
    lcd_control_config.master_config.addr_enable = false; /* address phase control for master */
    #ifdef SPI_ENABLE_DMA
    lcd_control_config.common_config.tx_dma_enable = true;
    #endif
    lcd_control_config.common_config.trans_mode = spi_trans_write_only;
    lcd_control_config.common_config.data_phase_fmt = spi_single_io_mode;
    lcd_control_config.common_config.dummy_cnt = spi_dummy_count_1;
    #ifdef SPI_ENABLE_DMA
    intc_m_enable_irq_with_priority(LCD_SPI_DMA_IRQ, 1);
    #endif
}

hpm_stat_t lcd_spi_send_cmd(uint8_t reg)
{
    hpm_stat_t stat;
    uint32_t retry = 0;
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 27, 0);
    if(one_byte_ctrl)
    {
        LCD_SPI->TRANSCTRL = one_byte_ctrl;
        LCD_SPI->CTRL |= SPI_CTRL_TXFIFORST_MASK | SPI_CTRL_RXFIFORST_MASK | SPI_CTRL_SPIRST_MASK;
        LCD_SPI->CMD = SPI_CMD_CMD_SET(0xff);
        while(LCD_SPI->STATUS & SPI_STATUS_TXFULL_MASK)
        {
            if (retry > 5000U) {
                return status_timeout;
            }
            retry++;
        }
        LCD_SPI->DATA = reg;
        retry = 0;
        while(LCD_SPI->STATUS & SPI_STATUS_SPIACTIVE_MASK)
        {
            if (retry > 5000U) {
                return status_timeout;
            }
            retry++;
        }
    }
    else
    {
        stat = spi_transfer(LCD_SPI, &lcd_control_config, NULL, NULL, &reg, 1, NULL, 0);
        return stat;
    }
    return status_success;
}

hpm_stat_t lcd_spi_send_data(uint8_t data)
{
    hpm_stat_t stat;
    uint32_t retry = 0;
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 27, 1);
    if(one_byte_ctrl)
    {
        LCD_SPI->TRANSCTRL = one_byte_ctrl;
        LCD_SPI->CTRL |= SPI_CTRL_TXFIFORST_MASK | SPI_CTRL_RXFIFORST_MASK | SPI_CTRL_SPIRST_MASK;
        LCD_SPI->CMD = SPI_CMD_CMD_SET(0xff);
        while(LCD_SPI->STATUS & SPI_STATUS_TXFULL_MASK)
        {
            if (retry > 5000U) {
                return status_timeout;
            }
            retry++;
        }
        LCD_SPI->DATA = data;
        retry = 0;
        while(LCD_SPI->STATUS & SPI_STATUS_SPIACTIVE_MASK)
        {
            if (retry > 5000U) {
                return status_timeout;
            }
            retry++;
        }
    }
    else
    {
        stat = spi_transfer(LCD_SPI, &lcd_control_config, NULL, NULL, &data, 1, NULL, 0);
        return stat;
    }
    return status_success;
}

hpm_stat_t lcd_spi_tx_buf(uint8_t *data, uint32_t length)
{
    hpm_stat_t stat;
    uint32_t trans_once,trans_count=0;
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 27, 1);
#ifdef SPI_ENABLE_DMA
    spi_tx_dma_trans_done = false;
    if(length > LCD_SPI_BYTES_PER_TRANS_MAX)
    {
        trans_once = LCD_SPI_BYTES_PER_TRANS_MAX;
    }
    else
    {
        trans_once = length;
    }
    stat = spi_setup_dma_transfer(LCD_SPI, &lcd_control_config, NULL, NULL, trans_once, 0);
    if (stat != status_success) {
        printf("spi setup dma transfer failed\n");
        while (1) {
        }
    }
    /* setup spi tx trigger dma transfer*/
#if PLACE_BUFF_AT_CACHEABLE
    if (l1c_dc_is_enabled()) {
        /* cache writeback for sent buff */
        uint32_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN((uint32_t)data);
        uint32_t aligned_end = HPM_L1C_CACHELINE_ALIGN_UP((uint32_t)data + length);
        uint32_t aligned_size = aligned_end - aligned_start;
        l1c_dc_writeback(aligned_start, aligned_size);
    }
#endif
    dmamux_config(LCD_SPI_DMAMUX, LCD_SPI_TX_DMAMUX_CH, LCD_SPI_TX_DMA_REQ, true);
    stat = spi_tx_trigger_dma(LCD_SPI_DMA, LCD_SPI_TX_DMA_CH, LCD_SPI, core_local_mem_to_sys_address(BOARD_RUNNING_CORE, (uint32_t)data), LCD_SPI_DMA_TRANS_DATA_WIDTH, trans_once);
    if(stat == status_success)
    {
        lcd_spi_dma_tx.trans_buf = data;
        lcd_spi_dma_tx.trans_size = length;
        lcd_spi_dma_tx.trans_count = trans_once;
    }
    else {
        printf("spi tx trigger dma failed\n");
        while (1) {
        }
    }
    #else
    trans_once = length - trans_count;
    if(trans_once > LCD_SPI_BYTES_PER_TRANS_MAX)
    {
        trans_once = LCD_SPI_BYTES_PER_TRANS_MAX;
    }
    do{
        if(status_success == spi_transfer(LCD_SPI, &lcd_control_config, NULL, NULL, data+trans_count, trans_once, NULL, 0))
        {
            trans_count += trans_once;
        }
    }while(trans_count<length);
    #endif
    return stat;
}

void lcd_reset(void)
{
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 26, 1);
    board_delay_us(100);
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 26, 0);
    board_delay_us(100);
    gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, 26, 1);
    board_delay_us(100);
}

void lcd_init(void)
{
    LCD_SPI_Init();
    HPM_IOC->PAD[IOC_PAD_PC26].FUNC_CTL = IOC_PC26_FUNC_CTL_GPIO_C_26;
    HPM_IOC->PAD[IOC_PAD_PC26].PAD_CTL = IOC_PAD_PAD_CTL_DS_SET(7) | IOC_PAD_PAD_CTL_PS_SET(1) | IOC_PAD_PAD_CTL_PE_SET(1);
    HPM_IOC->PAD[IOC_PAD_PC27].FUNC_CTL = IOC_PC27_FUNC_CTL_GPIO_C_27;
    HPM_IOC->PAD[IOC_PAD_PC27].PAD_CTL = IOC_PAD_PAD_CTL_DS_SET(7) | IOC_PAD_PAD_CTL_PS_SET(1) | IOC_PAD_PAD_CTL_PE_SET(1);
    gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_GET_PORT_INDEX(IOC_PAD_PC26), GPIO_GET_PIN_INDEX(IOC_PAD_PC26), 1);
    gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_GET_PORT_INDEX(IOC_PAD_PC27), GPIO_GET_PIN_INDEX(IOC_PAD_PC27), 1);
    one_byte_ctrl = SPI_TRANSCTRL_SLVDATAONLY_SET(lcd_control_config.slave_config.slave_data_only) |
                    SPI_TRANSCTRL_CMDEN_SET(lcd_control_config.master_config.cmd_enable) |
                    SPI_TRANSCTRL_ADDREN_SET(lcd_control_config.master_config.addr_enable) |
                    SPI_TRANSCTRL_ADDRFMT_SET(lcd_control_config.master_config.addr_phase_fmt) |
                    SPI_TRANSCTRL_TRANSMODE_SET(lcd_control_config.common_config.trans_mode) |
                    SPI_TRANSCTRL_DUALQUAD_SET(lcd_control_config.common_config.data_phase_fmt) |
                    SPI_TRANSCTRL_TOKENEN_SET(lcd_control_config.master_config.token_enable) |
                    SPI_TRANSCTRL_WRTRANCNT_SET(0) |
                    SPI_TRANSCTRL_TOKENVALUE_SET(lcd_control_config.master_config.token_value) |
                    SPI_TRANSCTRL_DUMMYCNT_SET(lcd_control_config.common_config.dummy_cnt) |
                    SPI_TRANSCTRL_RDTRANCNT_SET(0);
    lcd_reset();
    lcd_spi_send_cmd(0x11);
    board_delay_us(200);
    lcd_spi_send_cmd(0x36);
    #ifdef HORIZON
    lcd_spi_send_data(0x70);
    #else
    lcd_spi_send_data(0x00);
    #endif

    lcd_spi_send_cmd(0x3A);
    lcd_spi_send_data(0x05);

    lcd_spi_send_cmd(0xB2);
    lcd_spi_send_data(0x0C);
    lcd_spi_send_data(0x0C);
    lcd_spi_send_data(0x00);
    lcd_spi_send_data(0x33);
    lcd_spi_send_data(0x33);

    lcd_spi_send_cmd(0xB7);
    lcd_spi_send_data(0x35);

    lcd_spi_send_cmd(0xBB);
    lcd_spi_send_data(0x35);

    lcd_spi_send_cmd(0xC0);
    lcd_spi_send_data(0x2C);

    lcd_spi_send_cmd(0xC2);
    lcd_spi_send_data(0x01);

    lcd_spi_send_cmd(0xC3);
    lcd_spi_send_data(0x13);

    lcd_spi_send_cmd(0xC4);
    lcd_spi_send_data(0x20);

    lcd_spi_send_cmd(0xC6);
    lcd_spi_send_data(0x0F);

    lcd_spi_send_cmd(0xD0);
    lcd_spi_send_data(0xA4);
    lcd_spi_send_data(0xA1);

    lcd_spi_send_cmd(0xD6);
    lcd_spi_send_data(0xA1);

    lcd_spi_send_cmd(0xE0);
    lcd_spi_send_data(0xF0);
    lcd_spi_send_data(0x00);
    lcd_spi_send_data(0x04);
    lcd_spi_send_data(0x04);
    lcd_spi_send_data(0x04);
    lcd_spi_send_data(0x05);
    lcd_spi_send_data(0x29);
    lcd_spi_send_data(0x33);
    lcd_spi_send_data(0x3E);
    lcd_spi_send_data(0x38);
    lcd_spi_send_data(0x12);
    lcd_spi_send_data(0x12);
    lcd_spi_send_data(0x28);
    lcd_spi_send_data(0x30);

    lcd_spi_send_cmd(0xE1);
    lcd_spi_send_data(0xF0);
    lcd_spi_send_data(0x07);
    lcd_spi_send_data(0x0A);
    lcd_spi_send_data(0x0D);
    lcd_spi_send_data(0x0B);
    lcd_spi_send_data(0x07);
    lcd_spi_send_data(0x28);
    lcd_spi_send_data(0x33);
    lcd_spi_send_data(0x3E);
    lcd_spi_send_data(0x36);
    lcd_spi_send_data(0x14);
    lcd_spi_send_data(0x14);
    lcd_spi_send_data(0x29);
    lcd_spi_send_data(0x32);

    lcd_spi_send_cmd(0x21);

    lcd_spi_send_cmd(0x11);
    board_delay_us(200);
    lcd_spi_send_cmd(0x29);
}

void lcd_set_windows(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1)
{
    #ifdef HORIZON
        lcd_spi_send_cmd(0x2A);
        lcd_spi_send_data((x0>>8)&0xFF);
        lcd_spi_send_data(x0&0xFF);
        lcd_spi_send_data(((x1-1)>>8)&0xFF);
        lcd_spi_send_data((x1-1)&0xFF);

        lcd_spi_send_cmd(0x2B);
        lcd_spi_send_data(((y0+LCD_OFFSET)>>8)&0xFF);
        lcd_spi_send_data((y0+LCD_OFFSET)&0xFF);
        lcd_spi_send_data(((y1+LCD_OFFSET-1)>>8)&0xFF);
        lcd_spi_send_data((y1+LCD_OFFSET-1)&0xFF);
    #else
        lcd_spi_send_cmd(0x2A);
        lcd_spi_send_data(((x0+LCD_OFFSET)>>8)&0xFF);
        lcd_spi_send_data((x0+LCD_OFFSET)&0xFF);
        lcd_spi_send_data(((x1+LCD_OFFSET-1)>>8)&0xFF);
        lcd_spi_send_data((x1+LCD_OFFSET-1)&0xFF);

        lcd_spi_send_cmd(0x2B);
        lcd_spi_send_data((y0>>8)&0xFF);
        lcd_spi_send_data(y0&0xFF);
        lcd_spi_send_data(((y1-1)>>8)&0xFF);
        lcd_spi_send_data((y1-1)&0xFF);
#endif
}

void lcd_display(uint8_t *data, uint32_t length)
{
    uint32_t i;
    #ifdef HORIZON
    lcd_set_windows(0,0,LCD_HEIGHT,LCD_WIDTH);
    #else
    lcd_set_windows(0,0,LCD_WIDTH,LCD_HEIGHT);
    #endif
    lcd_spi_send_cmd(0x2C);
    lcd_spi_tx_buf(data, length);
    #ifdef SPI_ENABLE_DMA
    //while(spi_tx_dma_trans_done == false);//board_delay_ms(1);
    #endif
    //lcd_spi_send_cmd(0x29);
}

static void lv_flush_display_full(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    lcd_display(color_p, LCD_WIDTH*LCD_HEIGHT*2);
}

static void lv_disp_init(void)
{
    lv_disp_draw_buf_t *draw_buf = &lv_adapter_ctx.draw_buf;
    lv_disp_drv_t *disp_drv = &lv_adapter_ctx.disp_drv;

    lv_disp_draw_buf_init(draw_buf, lv_framebuffer0, lv_framebuffer1, LV_LCD_WIDTH * LV_LCD_HEIGHT);
    lv_disp_drv_init(disp_drv);

    disp_drv->hor_res = LV_LCD_WIDTH;
    disp_drv->ver_res = LV_LCD_HEIGHT;
    disp_drv->draw_buf = draw_buf;

    disp_drv->full_refresh = 1;
    disp_drv->flush_cb = lv_flush_display_full;
    disp_drv->user_data = &lv_adapter_ctx;

    lv_disp_drv_register(disp_drv);
}

void lv_adapter_init(void)
{
    lv_disp_init();
    lcd_set_windows(0,0,LCD_HEIGHT,LCD_WIDTH);
}
